using System;
using System.Collections.Generic;
using System.Collections.Specialized;

using System.ComponentModel.Composition;
using NAudio.Wave;
using NAudio.Wave.SampleProviders;

using iStudio.Structure.Engine;
using iStudio.Structure.Wave;

using WaveFormat = NAudio.Wave.WaveFormat;

namespace iStudio.Engine
{
    [Export (typeof (AudioEngine))]
    public class NAudioEngine : AudioEngine
    {
        #region Fields

        //
        // Playing stream (use to change position)
        //

        private INAudioStream _activeStream;

        //
        // Temp solution for FFT - aggregator gets samples which are drawn
        //

        private SampleAggregator _sampleAggregator;
        private NAudioMeter _meteringSampleProvider;

        //
        // Recording fields
        //

        private WaveIn _waveInDevice;
        private WaveTrack _recordTrack;

        //
        // Playing fields
        //

        private readonly IList<NAudioMixer> _waveChannelMixer;
        private NAudioMultiplexer _waveMultiplexer;
        private WaveOut _waveOutDevice;

        #endregion

        #region Constructor

        public NAudioEngine ()
        {          
            //
            // Wave channel mixer
            //

            _waveChannelMixer = new List<NAudioMixer>();
        }

        /// <summary>
        /// Initializes output for device number
        /// </summary>
        /// <param name="outDeviceNumber">Output device number from 0 to available - 1, if you put in -1 the most capable device will be chosen</param>
        /// <param name="inDeviceNumber">Device number from 0 to available - 1, if you put in -1 the most capable device will be chosen</param>
        public override void Initialize (int outDeviceNumber, int inDeviceNumber)
        {
            // Initialization check
            if ( (_waveOutDevice != null) || (_waveInDevice != null) )
            {
                throw new Exception("Unsupported operation: Engine is already initialized.");
            }

            //
            // Initialize output device
            //

            int waveOutChannels = 0;

            if (outDeviceNumber < 0)
            {
                int deviceCount = WaveOut.DeviceCount;

                for (int i = 0; i < deviceCount; i++)
                {
                    WaveOutCapabilities capabilities = WaveOut.GetCapabilities(i);
                    
                    if (capabilities.Channels > waveOutChannels)
                    {
                        outDeviceNumber = i;
                        waveOutChannels = capabilities.Channels;
                    }
                }
            }
            else if (outDeviceNumber > WaveOut.DeviceCount - 1)
            {
                throw new ArgumentException("Cannot init output device, it does not exist.");
            }

            //
            // Setup waveOut device
            //

            _waveOutDevice = new WaveOut
            {
                DeviceNumber = outDeviceNumber,
                DesiredLatency = 100
            };

            EngineOutput = (WaveOutputType) waveOutChannels;

            //
            // Initialize input device
            //

            int waveInChannels = 0;

            if (inDeviceNumber < 0)
            {
                int deviceCount = WaveIn.DeviceCount;

                for (int i = 0; i < deviceCount; i++)
                {
                    WaveInCapabilities capabilities = WaveIn.GetCapabilities(i);

                    if (capabilities.Channels > waveInChannels)
                    {
                        inDeviceNumber = i;
                        waveInChannels = capabilities.Channels;
                    }
                }
            }
            else if (inDeviceNumber > WaveIn.DeviceCount - 1)
            {
                throw new ArgumentException("Cannot init input device, it does not exist.");
            }
            else
            {
                waveInChannels = WaveIn.GetCapabilities(inDeviceNumber).Channels;
            }

            _waveInDevice = new WaveIn
            {
                DeviceNumber = inDeviceNumber,
                WaveFormat = new WaveFormat(44100, waveInChannels)
            };

            _waveInDevice.DataAvailable += WaveInStreamDataAvailable;

            _recordTrack = new WaveTrack(new Structure.Wave.WaveFormat
            {
                SampleRate = 44100,
                Channels = waveInChannels
            });

            //
            // Create mixers for each channel -> that is why each mixer has 1 channel
            //

            for (int i = 0; i < waveOutChannels; i++)
            {
                _waveChannelMixer.Add(new NAudioMixer());
            }

            //
            // Create multiplexer from mixers
            //

            _waveMultiplexer = new NAudioMultiplexer(_waveChannelMixer, (int)EngineOutput);

            //
            // Map outputs or use API
            //
        }

        protected override void TrackListOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
        {
            base.TrackListOnCollectionChanged(sender, notifyCollectionChangedEventArgs);

            //
            // If track was added on the list, we add wrappers for each mixer
            //

            if (notifyCollectionChangedEventArgs.Action == NotifyCollectionChangedAction.Add)
            {
                for (int i = 0; i < _waveChannelMixer.Count; i++)
                {
                    _waveChannelMixer[i].AddMixerInput(new NAudioTrack((WaveChannel)i, (WaveTrack)notifyCollectionChangedEventArgs.NewItems[0]));
                }
            }
        }

        #endregion

        #region ISpectrumPlayer

        public override bool GetFftData(float[] fftDataBuffer)
        {
            _sampleAggregator.GetFFTResults (fftDataBuffer);

            return isPlaying || isRecording;
        }

        public override int GetFftFrequencyIndex (int frequency)
        {
            double maxFrequency;

            if (_activeStream != null)
            {
                maxFrequency = _activeStream.WaveFormat.SampleRate / 2.0d;
            }
            else
            {
                maxFrequency = 44100; // Assume a default 44.1 kHz sample rate.
            }

            return Convert.ToInt32 ((frequency / maxFrequency) * (FftDataSize / (double) 2));
        }

        #endregion

        #region IWaveformPlayer

        public override double ChannelPosition
        {
            get
            {
                return channelPosition;
            }
            set
            {
                if (!inChannelSet)
                {
                    inChannelSet = true; // Avoid recursion

                    double oldValue = channelPosition;

                    double position = Math.Max (0, Math.Min (value, Project.Length));

                    if (!inChannelTimerUpdate && (_activeStream != null))
                    {
                        _activeStream.Position = (long)((position / Project.Length) * _activeStream.Length);
                    }

                    if (double.IsNaN (position))
                    {
                        position = 0.0;
                    }

                    channelPosition = position;

                    if (Math.Abs (oldValue - channelPosition) > Double.Epsilon)
                    {
                        NotifyPropertyChanged ("ChannelPosition");
                    }

                    inChannelSet = false;
                }
            }
        }

        #endregion

        #region IRecordPlayer

        protected override void ExecuteRecord ()
        {
            _sampleAggregator = new SampleAggregator (FftDataSize);

            Project.AddTrack (_recordTrack);

            _waveInDevice.StartRecording ();
        }

        protected override void ExecuteStopRecord ()
        {
            if (_waveInDevice == null)
            {
                return;
            }

            lock (_waveInDevice)
            {
                _waveInDevice.StopRecording ();

                _recordTrack = new WaveTrack (new Structure.Wave.WaveFormat
                                                  {
                                                      SampleRate = 44100,
                                                      Channels = 2
                                                  });
            }
        }


        #endregion

        #region ISoundPlayer

        public override OutputSampleProvider GetChannelOutputProviders(WaveChannel channel)
        {
            if ( ((int)channel < _waveChannelMixer.Count) && ((int)channel >= 0) )
            {
                return _waveChannelMixer[(int) channel].Output;
            }

            throw new ArgumentException("Channel not found");
        }

        public override OutputSampleProvider GetEngineOutputProviders()
        {
            return _waveMultiplexer.Output;
        }

        protected override void ExecuteStop ()
        {
            if (_waveOutDevice != null)
            {
                _waveOutDevice.Stop ();
            }
        }

        protected override void ExecutePause ()
        {
            if (_waveOutDevice != null)
            {
                _waveOutDevice.Pause ();
            }
        }

        protected override void ExecutePlay ()
        {
            if (_waveOutDevice != null)
            {
                if (_activeStream == null)
                {
                    _activeStream = _waveMultiplexer;

                    //
                    // Can cast active stream to sample provider here?
                    //

                    _meteringSampleProvider = new NAudioMeter(_activeStream, 1);
                    _meteringSampleProvider.StreamVolume += MeteringSampleProviderOnStreamVolume;

                    _activeStream = _meteringSampleProvider;

                    _sampleAggregator = new SampleAggregator(FftDataSize);

                    _waveOutDevice.Init(new SampleToWaveProvider(_activeStream));
                }

                //
                // If we were at certain channel position before, play from there
                //

                if ((ChannelPosition >= 0) && (Project.Length > ChannelPosition))
                {
                    // Calculate position
                    _activeStream.Position = (long)( (ChannelPosition / Project.Length) * _activeStream.Length);
                }
                else
                {
                    ChannelPosition = 0;
                }

                _waveOutDevice.Play ();
            }
        }

        

        #endregion

        #region Private Utility Methods

        protected override void CloseStream ()
        {
            if (_waveOutDevice != null)
            {
                _waveOutDevice.Dispose ();
                _waveOutDevice = null;
            }

            if (_waveInDevice != null)
            {
                _waveInDevice.Dispose ();
                _waveInDevice = null;
            }
        }

        #endregion

        #region Public Properties

        /*private IAudioProvider ActiveStream
        {
            get
            {
                return _activeStream;
            }
            set
            {
                WaveStream oldValue = _activeStream;

                _activeStream = value;

                if (oldValue != _activeStream)
                {
                    NotifyPropertyChanged ("ActiveStream");
                }
            }
        }*/

        #endregion

        #region Event Handlers

        private void MeteringSampleProviderOnStreamVolume(object sender, StreamVolumeEventArgs streamVolumeEventArgs)
        {
            _sampleAggregator.Add (streamVolumeEventArgs.MaxSampleValues[0], streamVolumeEventArgs.MaxSampleValues[1]);
        }

        private void WaveInStreamDataAvailable (object sender, WaveInEventArgs e)
        {
            int bytesRecorded = (int) (Math.Floor (e.BytesRecorded / (double) 4)) * 4;

            // Output availible samples into aggregator
            // We expect 16 bits recording depth and stereo sound
            for (int i = 0; i < bytesRecorded; i = i + 4)
            {
                short left = BitConverter.ToInt16 (e.Buffer, i);
                short right = BitConverter.ToInt16 (e.Buffer, i + 2);

                _recordTrack.TrackChunks[0].WaveBuffer.Add (left / 32768.0f);
                _recordTrack.TrackChunks[1].WaveBuffer.Add (right / 32768.0f);
                _sampleAggregator.Add (left / 32768.0f, right / 32768.0f);
            }
        }

        protected override void PositionTimerTick (object sender, EventArgs e)
        {
            if (_activeStream != null)
            {

                //
                // Calculate repeat stop position
                //

                long repeatStopPosition = (long)((SelectionEnd.TotalSeconds / Project.Length) * _activeStream.Length);

                if (((SelectionEnd - SelectionBegin) >= TimeSpan.FromMilliseconds(RepeatThreshold)) && _activeStream.Position >= repeatStopPosition)
                {
                    _sampleAggregator.Clear();

                    ChannelPosition = SelectionBegin.TotalSeconds;

                    // To repeat the selection if needed
                    if (Repeat == false)
                    {
                        Pause();
                    }
                }

                //
                // Handle end of track
                //

                else if (_activeStream.Position >= _activeStream.Length)
                {
                    ChannelPosition = 0;

                    if (repeat == false)
                    {
                        Pause();
                    }
                }
                else
                {
                    //
                    // Write position
                    //

                    inChannelTimerUpdate = true;

                    ChannelPosition = (_activeStream.Position / (double) _activeStream.Length) * Project.Length;

                    inChannelTimerUpdate = false;
                }
            }
        }

        #endregion

        /*
        #region Waveform Generation

        protected override void WaveformGenerateWorkerDoWork(object sender, DoWorkEventArgs e)
        {
            
            WaveformGenerationParams waveformParams = e.Argument as WaveformGenerationParams;

            MemoryStream memory = new MemoryStream(_fileData.ToArray());

            WaveStream waveformStream;

            if (fileExtension == ".mp3")
            {
                waveformStream = new Mp3FileReader(memory);
            }
            else
            {
                waveformStream = new WaveFileReader(memory);
            }

            if (waveformStream.WaveFormat.BitsPerSample != 16)
            {
                //NAudio.WaveFormat format = new WaveFormat(waveformStream.WaveFormat.SampleRate, 16, waveformStream.WaveFormat.Channels);

                //waveformStream = new WaveFormatConversionStream(format, waveformStream);
                //waveformStream = new BlockAlignReductionStream(waveformStream);
            }

            WaveChannel32 waveformInputStream = new WaveChannel32(waveformStream);

            waveformInputStream.Sample += WaveStreamSample;

            const int frameLength = 64;
            int frameCount = (int)(waveformInputStream.Length / (double)frameLength);
            int waveformLength = frameCount * 2;

            byte[] readBuffer = new byte[frameLength];

            _waveformAggregator = new SampleAggregator(frameLength);

            float maxLeftPointLevel = float.MinValue;
            float maxRightPointLevel = float.MinValue;

            int currentPointIndex = 0;

            if (waveformParams != null)
            {
                float[] waveformCompressedPoints = new float[Math.Min(waveformParams.Points, waveformLength)];
                List<float> data = new List<float>();
                List<int> waveMaxPointIndexes = new List<int>();

                for (int i = 1; i <= Math.Min(waveformParams.Points, waveformLength); i++)
                {
                    waveMaxPointIndexes.Add((int)Math.Round(waveformLength * (i / (double)waveformParams.Points), 0));
                }

                int readCount = 0;

                while (currentPointIndex * 2 < Math.Min (waveformParams.Points, waveformLength) )
                {
                    waveformInputStream.Read(readBuffer, 0, readBuffer.Length);

                    data.Add(_waveformAggregator.LeftMaxVolume);
                    data.Add(_waveformAggregator.RightMaxVolume);

                    if (_waveformAggregator.LeftMaxVolume > maxLeftPointLevel)
                    {
                        maxLeftPointLevel = _waveformAggregator.LeftMaxVolume;
                    }

                    if (_waveformAggregator.RightMaxVolume > maxRightPointLevel)
                    {
                        maxRightPointLevel = _waveformAggregator.RightMaxVolume;
                    }

                    if (readCount > waveMaxPointIndexes[currentPointIndex])
                    {
                        waveformCompressedPoints[(currentPointIndex * 2)] = maxLeftPointLevel;
                        waveformCompressedPoints[(currentPointIndex * 2) + 1] = maxRightPointLevel;

                        maxLeftPointLevel = float.MinValue;
                        maxRightPointLevel = float.MinValue;

                        currentPointIndex++;
                    }

                    if (readCount % 3000 == 0)
                    {
                        float[] clonedData = (float[])waveformCompressedPoints.Clone();

                        Application.Current.Dispatcher.Invoke(new Action(() =>
                                                                             {
                                                                                 WaveformData = clonedData;
                                                                             }));
                    }

                    if (WaveformGenerateWorker.CancellationPending)
                    {
                        e.Cancel = true;

                        break;
                    }

                    readCount++;
                }

                float[] finalClonedData = (float[])waveformCompressedPoints.Clone();

                Application.Current.Dispatcher.Invoke(new Action(() =>
                                                                     {
                                                                         fullLevelData = data.ToArray();

                                                                         WaveformData = finalClonedData;
                                                                     }));
            }

            waveformInputStream.Close();
            waveformInputStream.Dispose();

            waveformStream.Close();
            waveformStream.Dispose();
        }

        #endregion*/
    }
}